package utils.xml;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import utils.path.ConfigPath;
import utils.path.support.ClassConfigPath;
import utils.reader.support.XMLDocumentResourceReader;
import utils.resource.support.XMLDocumentResource;
import utils.test.Assert;

/**
 * 主要操作DOM API
 *
 */
public final class DomUtils {
	
	private DomUtils() {
		
	}
	
	/**
	 * 结点匹配回调接口
	 */
	public interface NodeCallBack{
		boolean match(Node node);
	}
	
	/**
	 * 取得文档根节点
	 * @param doc 输入整个文档
	 * @return
	 */
	public static Node getDocumentRootElement(Document doc){
		Assert.isNotNull(doc, "Document is null.");
		return doc.getDocumentElement();
	}
	
	/**
	 * 以节点Map的形式返回属性集
	 * @param node
	 * @return
	 */
	public static NamedNodeMap getNodeAttrs(Node node){
		Assert.isNotNull(node, "Node is null.");
		NamedNodeMap attrs = node.getAttributes();
		return attrs;
	}
	
	/**
	 * 属性名找值
	 * @param node
	 * @param name
	 * @return*/
	public static String getNodeAttrValByName(Node node,String name){
		Assert.isNotNull(node, "Node is null.");
		Assert.isNotNull(name, "Attribute name is null.");
		NamedNodeMap attrs = node.getAttributes();
		for (int i = 0; i < attrs.getLength(); i++) {
			String attrsTag = attrs.item(i).getNodeName();
			String attrsValue = attrs.item(i).getNodeValue();
			if (name.equals(attrsTag)) {
				return attrsValue;
			}
		}
		return null;
	}
	
	/**
	 * 查找节点指定属性节点
	 * @param node
	 * @return
	 */
	public static Node getNodeAttrByName(Node node,String name){
		Assert.isNotNull(node, "Node is null.");
		Assert.isNotNull(name, "Attribute name is null.");
		NamedNodeMap attrs = node.getAttributes();
		if (attrs!=null&&attrs.getLength() > 0) {
			return attrs.getNamedItem(name);
		}
		return null;
	}
	
	/**
	 * 根据规则返回节点集合
	 * 
	 * <configuration>
		   <database>
		      <username>dbuser</username>
		      <password>secret</password>
		   </database>
		   <database>
		      <username>dbuser</username>
		      <password>secret</password>
		   </database>
	　　</configuration>
	　　xpath = "/configuration/database"
	 * @param xpath
	 * @param doc
	 * @return
	 */
	public static NodeList getNodesByXPath(String xpath,Document doc){
		XPathFactory xpfactory = XPathFactory.newInstance();
		XPath path = xpfactory.newXPath();
		try {
			return (NodeList) path.evaluate(xpath, doc, XPathConstants.NODESET);
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 根据规则返回节点
	 * <configuration>
		   <database>
		      <username>dbuser</username>
		      <password>secret</password>
		   </database>
	　　</configuration>
		xpath:/configuration/database/username 返回　dbuser字符串
	 * @param xpath
	 * @param doc
	 * @return
	 */
	public static String getNodeValueByXPath(String xpath,Document doc){
		XPathFactory xpfactory = XPathFactory.newInstance();
		XPath path = xpfactory.newXPath();
		try {
			return path.evaluate(xpath, doc);
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 是否是最终节点元素
	 * 最终节点定义形式如下：
	 * 1.<t>字符<t>
	 * 2.<t/>
	 * (不再有子元素标签)
	 * 
	 * 1.若不是一个元素节点类型的直接返false(不是一个元素节点，可能是文本节点，或其它);
	 * 2.剩下的只能是元素节点,若<tr>形式直接返回true
	 * 3.若<t><d></d></t>，找下级子节点，若存在一个是节点类型的都视为不是元素结点;
	 * 4.里面是文本的就是一个节点，判不判断都可以，剩下只有这种情况出现；
	 * @param node
	 * @return
	 */
	public static boolean isFinalElement(Node node){
		if (node.getNodeType() != Node.ELEMENT_NODE) {
			return false;
		}
		if (!node.hasChildNodes()) {
			//<t/>形式
			return true;
		}
		//剩下的情况只有是<t>字符</t>形式
		NodeList nodeList = node.getChildNodes();
		//循环下级子节点，若找到一个不是文本的节点类型都视为还有子节点
		for (int i = 0; i < nodeList.getLength(); i++) {
			Node childNode = nodeList.item(i);
			if (childNode.getNodeType() == Node.ELEMENT_NODE) {
				return false;
			}
		}
		if (nodeList.item(0).getNodeType() == Node.TEXT_NODE) {
			return true;
		}
		return false;
	}
	
	/**
	 * 取得元素节点的值
	 * @param elm
	 */
	public static String getElementValue(Node elm){
		NodeList nodeList = elm.getChildNodes();
		return nodeList.item(0).getNodeValue();
	}
	
	/**
	 * 取得节点下所有子节点
	 * 可能是 subelements, text, comments, or other nodes
	 * @param node 节点
	 * @param callBack 匹配回调接口，为空时，默认只取元素节点
	 * @return
	 */
	public static List<Node> getChildNodes(Node node,NodeCallBack ... callBack){
		if (node == null) {
			return null;
		}
		List<Node> nodes = new ArrayList<Node>();
		NodeList children = node.getChildNodes();
		for (int i = 0; i < children.getLength(); i++) {
			Node child = children.item(i);
			if (callBack != null && callBack.length > 0) {
				NodeCallBack cb = callBack[0];
				//外部回调匹配规则选取子节点
				if (cb.match(child)) {
					nodes.add(child);
				}
			}else{
				//默认处理
				if (child.getNodeType() == Node.ELEMENT_NODE) {
					nodes.add(child);
				}
			}
		}
		return nodes;
	}
	
	/**
	 * 查找节点下级一级节点标签为tagName的结点是否存在
	 * @param node 节点
	 * @param tagName　标签名
	 * @param isEle 只查找元素标签　true 表示是，false　表示否
	 * @return
	 */
	public static boolean hasChildNodeTag(Node node,String tagName,boolean isEle){
		if (getChildNodeByTag(node, tagName, isEle) == null) {
			return false;
		}else{
			return true;
		}
	}
	
	/**
	 * 查找节点下级一级节点标签为tagName的结点
	 * @param node 节点
	 * @param tagName 标签名
	 * @param matchFinalEle 只查找终节点元素　true 表示是，false　表示否
	 * @return
	 */
	public static Node getChildNodeByTag(Node node,String tagName,boolean matchFinalEle){
		Assert.isNotNull(node, "node 不能为空!");
		Assert.isNotNull(tagName, "tagName 不能为空!");
		NodeList nodeList = node.getChildNodes();
		for (int i = 0; i < nodeList.getLength(); i++) {
			Node childNode = nodeList.item(i);
			String nodeTagName = childNode.getNodeName();
			if (childNode instanceof Element && tagName.equals(nodeTagName)) {
				if (matchFinalEle && isFinalElement(childNode)) {
					//若查找终结点元素 且 节点确定是终结点
					return childNode;
				}else{
					return childNode;
				}
			}
		}
		return null;
	}
	
	/**
	 * 查找节点下级一级节点标签为tagName的所有子结点
	 * @param node 节点
	 * @param tagName 标签名
	 * @param matchFinalEle 只查找终节点元素　true 表示是，false　表示否
	 * @return
	 */
	public static List<Node> getChildNodesByTag(Node node,String tagName,boolean matchFinalEle){
		Assert.isNotNull(node, "node 不能为空!");
		Assert.isNotNull(tagName, "tagName 不能为空!");
		List<Node> nodes = new ArrayList<Node>();
		NodeList nodeList = node.getChildNodes();
		for (int i = 0; i < nodeList.getLength(); i++) {
			Node childNode = nodeList.item(i);
			String nodeTagName = childNode.getNodeName();
			if (childNode instanceof Element && tagName.equals(nodeTagName)) {
				if (matchFinalEle && isFinalElement(childNode)) {
					//若查找终结点元素 且 节点确定是终结点
					nodes.add(childNode);
				}else{
					nodes.add(childNode);
				}
			}
		}
		return nodes;
	}
	
	public static void main(String[] args) {
		ConfigPath path = new ClassConfigPath("bean-database-context-template.xml");
		XMLDocumentResourceReader reader = new XMLDocumentResourceReader();
		XMLDocumentResource resource = (XMLDocumentResource)reader.read(path.getLocation()[0]);
		Document doc = resource.getDocument();
		Node root = DomUtils.getDocumentRootElement(doc);

		List<Node> nodes = DomUtils.getChildNodes(root);
		for (Node node : nodes) {
			boolean isEle = isFinalElement(node);
			System.out.println("是元素节点："+isEle);
			System.out.println(node.getNodeName());
			if (isEle) {
				String el = getElementValue(node);
				System.out.println("元素节点值："+el);
			}
			
		}
		String s = getNodeValueByXPath("/DataBase/DataBaseAdvancedConfig/DataBaseThreadLocal", doc);
		System.out.println("规则取值："+s);
		
		boolean f = hasChildNodeTag(root, "DataSourceConfigs", false);
		System.out.println("存在子节点："+f);
	}
}
